// 15 面向对象程序设计
/**
 * 面向对象程序设计基于三个基本概念：数据抽象、继承和动态绑定。第7章已经介绍了数据抽象的知识，本章将介绍继承和动态绑定。
 * 继承和动态绑定对程序的编写有两方面的影响：一是我们可以更容易地定义与其他类相似但不完全相同的新类；二是在使用这些彼此相似的类编写程序时，我们可以在一定程度上忽略掉它们的区别。
 * 在很多程序中都存在着一些相互关联但是有细微差别的概念。
 * 例如，书店中不同书籍的定价策略可能不同：有的书籍按原价销售，有的则打折销售。有时，我们给那些购买书籍超过一定数量的顾客打折；另一些时候，则只对前多少本销售的书籍打折，之后就调回原价，等等。
 * 面向对象的程序设计（OOP）适用于这类应用。
 */

#include <iterator>
#include <vector>
#include <list>
#include <deque>
#include <forward_list>
#include <string>
#include <array>
#include <stack>
#include <queue>
#include <algorithm>
#include <numeric>
using std::swap;
using std::vector, std::list, std::deque, std::forward_list, std::string, std::array, std::stack, std::queue;
#include "../Chapter07/Sales_data.h"
#include <iostream>
using std::begin, std::cbegin, std::end, std::cend, std::find, std::accumulate, std::equal, std::fill, std::fill_n, std::back_inserter;
using std::cin, std::cout, std::endl;
using std::copy, std::replace, std::replace_copy;
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <memory>
#include <new>
using namespace std;
#include "../Chapter13/13.5.cc" // 不能编译，因为重复定义的main函数
#include "../Chapter12/12.1.6.cc" // 不能编译，因为重复定义的main函数
#include <functional>

// 为了对之前提到的不同定价策略建模，我们首先定义一个名为Quote的类，并将它作为层次关系中的基类。
// Quote的对象表示按原价销售的书籍。Quote派生出另一个名为Bulk_quote的类，它表示可以打折销售的书籍。
class Quote
{
public:
    Quote() = default; // 关于=default参见7.1.4节（第237页）
    Quote(const string &book, double sales_price) : bookNo(book), price(sales_price) {}
    // isbn（ ），返回书籍的ISBN编号。该操作不涉及派生类的特殊性，因此只定义在Quote类中。
    std::string isbn() const { return bookNo; };
    // net_price（size_t），返回书籍的实际销售价格，前提是用户购买该书的数量达到一定标准。这个操作显然是类型相关的，Quote和Bulk_quote都应该包含该函数。
    virtual double net_price(std::size_t n) const { return n*price; };
    // 在C++语言中，基类将类型相关的函数与派生类不做改变直接继承的函数区分对待。对于某些函数，基类希望它的派生类各自定义适合自身的版本，此时基类就将这些函数声明成虚函数（virtual function）。
    virtual ~Quote() = default; // 对析构函数进行动态绑定。根节点的类通常都会定义一个虚析构函数。
private:
    std::string bookNo; // 书籍的ISBN编号
protected:
    double price = 0.0; // 代表普通状态下不打折的价格
};
// 用于保存折扣值和购买量的类，派生类使用这些数据可以实现不同的价格策略
class Disc_quote : public Quote
{
public:
    Disc_quote() = default;
    Disc_quote(const string &book, double price, size_t qty, double disc) : Quote(book, price), quantity(qty), discount(disc) {}
    double net_price(size_t) const = 0; // 纯虚函数,含有纯虚函数的类是抽象基类，不能实例化
protected:
    size_t quantity = 0; // 折扣适用的购买量
    double discount = 0.0; // 表示折扣的小数值
};
// 派生类必须通过使用类派生列表（class derivation list）明确指出它是从哪个（哪些）基类继承而来的。
// 类派生列表的形式是：首先是一个冒号，后面紧跟以逗号分隔的基类列表，其中每个基类前面可以有访问说明符：
class Bulk_quote : public Disc_quote // Bulk_quote继承了Quote
{
public:
    Bulk_quote() = default;
    Bulk_quote(const string &book, double p, size_t qty, double disc) : Disc_quote(book,p,qty,disc) {}; // 使用其基类构造函数初始化基类成员
    // 覆盖基类的函数版本以实现基于大量购买的折扣策略
    double net_price(std::size_t) const override; // 形参列表后增加override关键字
private:
    // std::size_t min_qty = 0; // 使用折扣策略的最低购买量
    // double discount = 0.0;   // 以小数表示的折扣额
    // 如前所述，每个类各自控制其对象的初始化过程。因此，即使Bulk_quote没有自己的数据成员，它也仍然需要像原来一样提供一个接受四个参数的构造函数。
};
// 计算并打印销售给定数量的某种书籍所得的费用
double print_total(ostream &os, const Quote &item, size_t n)
{
    // 根据传入item形参的对象类型调用Quote::net_price或者Bulk_quote::net_price
    double ret = item.net_price(n);
    os << "ISBN: " << item.isbn() // 调用Quote::isbin
       << " # sold: " << n << " total due: " << ret << endl;
    return ret;
}
// 如果达到了购买书籍的某个最低限量值，就可以享受折扣价格了
double Bulk_quote::net_price(std::size_t cnt) const
{
    if(cnt > quantity)
        return cnt * (1-discount) * price;
    else
        return cnt * price;
}

int main()
{
    // 15.4 抽象基类
    // 我们可以定义一个新的名为Disc_quote的类来支持不同的折扣策略，其中Disc_quote负责保存购买量的值和折扣值。
    // 其他的表示某种特定策略的类（如Bulk_quote）将分别继承自Disc_quote，每个派生类通过定义自己的net_price函数来实现各自的折扣策略。
    // 我们可以在Disc_quote类中不定义新的net_price，此时，Disc_quote将继承Quote中的net_price函数。然而，这样的设计可能导致用户编写出一些无意义的代码。
    // 用户可能会创建一个Disc_quote对象并为其提供购买量和折扣值，如果将该对象传给一个像print_total这样的函数，则程序将调用Quote版本的net_price。
    // 显然，最终计算出的销售价格并没有考虑我们在创建对象时提供的折扣值，因此上述操作毫无意义。

    // 纯虚函数
    // 和普通的虚函数不一样，一个纯虚函数无须定义。我们通过在函数体的位置（即在声明语句的分号之前）书写=0就可以将一个虚函数说明为纯虚函数。其中，=0只能出现在类内部的虚函数声明语句处：
    // 值得注意的是，我们也可以为纯虚函数提供定义，不过函数体必须定义在类的外部。也就是说，我们不能在类的内部为一个=0的函数提供函数体。

    // 含有纯虚函数的类是抽象基类
    // 含有（或者未经覆盖直接继承）纯虚函数的类是抽象基类（abstract base class）。抽象基类负责定义接口，而后续的其他类可以覆盖该接口。
    // 因为Disc_quote将net_price定义成了纯虚函数，所以我们不能定义Disc_quote的对象。我们可以定义Disc_quote的派生类的对象，前提是这些类覆盖了net_price函数：
    //Disc_quote discounted; // 错误，不能定义含有纯虚函数的抽象基类的对象
    // 我们不能创建抽象基类的对象。

    // 派生类构造函数只初始化它的直接基类
    // 如前所述，每个类各自控制其对象的初始化过程。因此，即使Bulk_quote没有自己的数据成员，它也仍然需要像原来一样提供一个接受四个参数的构造函数。

    // 关键概念：重构
    // 在Quote的继承体系中增加Disc_quote类是重构（refactoring）的一个典型示例。
    // 重构负责重新设计类的体系以便将操作和/或数据从一个类移动到另一个类中。对于面向对象的应用程序来说，重构是一种很普遍的现象。
    // 值得注意的是，即使我们改变了整个继承体系，那些使用了Bulk_quote或Quote的代码也无须进行任何改动。不过一旦类被重构（或以其他方式被改变），就意味着我们必须重新编译含有这些类的代码了。
    

    return 0;
}